/*
    http://bhepple.freeshell.org

    Copyright (C) 2010 Bob Hepple <bhepple@freeshell.org>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/

/**
 * \file tbs_test.c unit tests for tbs.c

 * To run from the source directory:
 *
 * libtool --mode=execute valgrind --leak-check=full ./tests/tbs_test
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tbs.h>
#include "./tb_expect.h"

void *xcalloc (size_t nmemb, size_t size)
{
    void *retval = calloc(nmemb, size);

    /* Normally (ie as specified on GNU pages), xcalloc etc do not
     * return when the heap is exhausted. tba does not make that
     * assumption and provides code for the heap exhaustion case.
     *
     * For example, a longjmp could be used here to take us to a
     * recovery routine ie a routine that has previously called
     * setjmp(). It could free some stuff (maybe a buffer alloc'd as a
     * safety reserve) and then either exit safely or try to continue,
     * perhaps with a warning.
     *
     * extern jmp_buf               NoMoreHeap; 
     * if (!retval) longjmp(NoMoreHeap, 1); */

    return(retval);
}

void *xmalloc (size_t size)
{
    void *retval = malloc(size);
    /* if (!retval) longjmp(NoMoreHeap, 1); */
    return(retval);
}

void *xrealloc(void *oldbuf, size_t size)
{
    void *retval = NULL;

    retval = realloc(oldbuf, size);

    /* if (!retval) longjmp(NoMoreHeap, 1); */
    return(retval);
}

void xfree(void *buf)
{
    free(buf);
}

void printfStr(char *label, tbs  s)
{
    if (s) {
        printf("%s = \"%s\" length = %d, maxlen = %d\n", label,
               tbs_get_str(s), tbs_get_length(s), 
               tbs_get_maxlen(s));
    }
}
#define TEST_STRING "Hello world"

void header(char *s, int error)
{
    static int last_error = 0;
    if (error == last_error) {
        printf("ok\n");
    } else {
        last_error = error;
    }

    printf("*******************************\n%s:\n", s);
}

#define PRINT_CLUES                                                     \
    printfStr("a", a);                                                  \
    printfStr("b", b);                                                  \
    printfStr("c", c);                                                  \
    errors++;

int unittest(int min_maxlen)
{
    tbs a = NULL, b = NULL, c = NULL;
    int errors = 0;
    int value;
    char *p = NULL;
    int len;
    int i;
    int pos;

    printf("\nRunning unittest with min_maxlen = %d\n", min_maxlen);

    tbs_set_min_maxlen(min_maxlen);

    header("tbs_new_maxlen", errors);
    pEXPECT(a = tbs_new_maxlen(strlen(TEST_STRING)));
    tbs_free(&a);
    pEXPECT(a = tbs_new_maxlen(0));
    tbs_free(&a);
    npEXPECT(a = tbs_new_maxlen(-1));

    header("tbs_new", errors);
    pEXPECT(a = tbs_new(TEST_STRING));
    tbs_free(&a);
    pEXPECT(a = tbs_new(""));
    tbs_free(&a);
    pEXPECT(a = tbs_new(NULL));
    tbs_free(&a);

    header("tbs_get_maxlen", errors);
    pEXPECT(a = tbs_new(""));
    iEXPECT(tbs_get_maxlen(a), min_maxlen);
    tbs_free(&a);
    pEXPECT(a = tbs_new(TEST_STRING));
    liEXPECT(tbs_get_maxlen(a), MAX(strlen(TEST_STRING), min_maxlen));
    tbs_free(&a);
    iEXPECT(tbs_get_maxlen(a), 0); /* a == NULL */

    header("tbs_get_length", errors);
    pEXPECT(a = tbs_new(""));
    iEXPECT(tbs_get_length(a), 0);
    tbs_free(&a);
    pEXPECT(a = tbs_new(TEST_STRING));
    liEXPECT(tbs_get_length(a), strlen(TEST_STRING));
    tbs_free(&a);
    iEXPECT(tbs_get_length(a), 0); /* a == NULL */

    header("tbs_get_str", errors);
    npEXPECT(tbs_get_str(NULL));
    pEXPECT(a = tbs_new(TEST_STRING));
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING), 0);
    tbs_free(&a);

    header("tbs_append_str", errors);
    pEXPECT(tbs_append_str(&a, TEST_STRING)); /* a == NULL */
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING), 0);
    pEXPECT(tbs_append_str(&a, TEST_STRING));
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING TEST_STRING), 0);
    pEXPECT(tbs_append_str(&a, ""));
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING TEST_STRING), 0);
    tbs_free(&a);

    header("tbs_set_str", errors);
    pEXPECT(tbs_set_str(&a, TEST_STRING)); /* a == NULL */
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING), 0);
    pEXPECT(tbs_set_str(&a, TEST_STRING TEST_STRING));
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING TEST_STRING), 0);
    pEXPECT(tbs_set_str(&a, ""));
    iEXPECT(strcmp(tbs_get_str(a), ""), 0);
    pEXPECT(tbs_set_str(&a, NULL));
    iEXPECT(strcmp(tbs_get_str(a), ""), 0); /* unchanged */
    tbs_free(&a);

    header("tbs_get_substr", errors);
    pEXPECT(a = tbs_new(TEST_STRING));
    pEXPECT(c = tbs_get_substr(a, 0, 3));
    iEXPECT(strcmp(tbs_get_str(c), "Hel"), 0);
    tbs_free(&c);
    pEXPECT(c = tbs_get_substr(a, 1, 3));
    iEXPECT(strcmp(tbs_get_str(c), "ell"), 0);
    tbs_free(&c);
    pEXPECT(c = tbs_get_substr(a, strlen(TEST_STRING), -5));
    iEXPECT(strcmp(tbs_get_str(c), "world"), 0);
    tbs_free(&c);
    pEXPECT(c = tbs_get_substr(a, 3, -5));
    iEXPECT(strcmp(tbs_get_str(c), "Hel"), 0);
    tbs_free(&c);
    pEXPECT(c = tbs_get_substr(a, 0, 300));
    iEXPECT(strcmp(tbs_get_str(c), TEST_STRING), 0);
    tbs_free(&c);
    pEXPECT(c = tbs_get_substr(a, -1, 3));
    iEXPECT(strcmp(tbs_get_str(c), "d"), 0);
    tbs_free(&c);
    pEXPECT(c = tbs_get_substr(a, strlen(TEST_STRING), 3));
    iEXPECT(strcmp(tbs_get_str(c), ""), 0);
    tbs_free(&a);
    tbs_free(&c);

    header("tbs_set_substr", errors);
    pEXPECT(b = tbs_new(TEST_STRING));
    pEXPECT(a = tbs_set_substr(NULL, 100, b));
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING), 0);
    tbs_free(&a);

    pEXPECT(tbs_set_substr(&a, 100, b)); /* a == NULL */
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING), 0);

    pEXPECT(tbs_set_substr(&a, 0, b)); /* a == TEST_STRING, b == TEST_STRING */
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING), 0);

    pEXPECT(tbs_set_str(&a, TEST_STRING));
    pEXPECT(tbs_set_str(&b, ""));
    pEXPECT(tbs_set_substr(&a, 5, b)); /* a == TEST_STRING, b == "" */
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING), 0);
    tbs_free(&a);

    pEXPECT(a = tbs_set_substr(NULL, 100, b)); /* b == "" */
    pEXPECT(tbs_set_substr(&a, 0, NULL)); /* a == "" */
    iEXPECT(strcmp(tbs_get_str(a), ""), 0);

    pEXPECT(tbs_set_str(&a, TEST_STRING));
    pEXPECT(tbs_set_str(&b, "123"));
    pEXPECT(a = tbs_set_substr(&a, 3, b));
    iEXPECT(strcmp(tbs_get_str(a), "Hel123world"), 0);

    pEXPECT(tbs_set_str(&a, TEST_STRING));
    pEXPECT(tbs_set_str(&b, "123"));
    pEXPECT(a = tbs_set_substr(&a, 10, b));
    iEXPECT(strcmp(tbs_get_str(a), "Hello worl123"), 0);

    pEXPECT(tbs_set_str(&a, TEST_STRING));
    pEXPECT(tbs_set_str(&b, "123"));
    pEXPECT(a = tbs_set_substr(&a, -8, b));
    iEXPECT(strcmp(tbs_get_str(a), "123lo world"), 0);

    tbs_free(&a);
    tbs_free(&b);

    header("tbs_fill", errors);
    tbs_fill(NULL, 'x');
    pEXPECT(a = tbs_new(TEST_STRING));
    tbs_fill(a, 'x');

    len = tbs_get_maxlen(a);
    p = xmalloc(len + 1);
    for (i = 0; i < len; i++) p[i] = 'x';
    p[len] = 0;
    iEXPECT(strcmp(tbs_get_str(a), p), 0);
    xfree(p); p = NULL;

    tbs_fill(a, 0);
    iEXPECT(strcmp(tbs_get_str(a), ""), 0);
    tbs_free(&a);

    header("tbs_resize", errors);
    pEXPECT(a = tbs_new(TEST_STRING));
    pEXPECT(tbs_resize(&a, min_maxlen + 100));
    iEXPECT(tbs_get_maxlen(a), min_maxlen + 100);
    pEXPECT(tbs_resize(&a, 1));
    iEXPECT(tbs_get_maxlen(a), min_maxlen + 100);
    tbs_free(&a);

    header("tbs_delchars", errors);
    pEXPECT(a = tbs_new(TEST_STRING)); /*   Hello world */
    pEXPECT(tbs_delchars(&a, 0, 1));       /*   ello world */
    pEXPECT(tbs_delchars(&a, 2, 1));       /*   elo world */
    pEXPECT(tbs_delchars(&a, -1, 100));    /*   elo worl */
    pEXPECT(tbs_delchars(&a, 500, 1)); /* NOP - elo worl */
    pEXPECT(tbs_delchars(&a, -2, 1)); /* NOP -  elo worl */
    iEXPECT(strcmp(tbs_get_str(a), "elo worl"), 0);
    tbs_free(&a);

    header("tbs_insert", errors);
    pEXPECT(b = tbs_new("1"));
    pEXPECT(a = tbs_insert(NULL, 0, b));
    iEXPECT(strcmp(tbs_get_str(a), "1"), 0);
    tbs_free(&a);
    pEXPECT(tbs_insert(&a, 0, b));
    iEXPECT(strcmp(tbs_get_str(a), "1"), 0);

    pEXPECT(tbs_set_str(&a, TEST_STRING));
    pEXPECT(tbs_insert(&a, 0, b));  /* "1Hello world" */
    iEXPECT(strcmp(tbs_get_str(a), "1Hello world"), 0);
    pEXPECT(tbs_set_str(&b, "2"));
    pEXPECT(tbs_insert(&a, 2, b));  /* "1H2ello world" */
    iEXPECT(strcmp(tbs_get_str(a), "1H2ello world"), 0);
    pEXPECT(tbs_set_str(&b, "3"));
    pEXPECT(tbs_insert(&a, -1, b)); /* "1H2ello world3" */
    iEXPECT(strcmp(tbs_get_str(a), "1H2ello world3"), 0);
    pEXPECT(tbs_set_str(&b, "4"));
    pEXPECT(tbs_insert(&a, 12, b)); /* "1H2ello worl4d3" */
    iEXPECT(strcmp(tbs_get_str(a), "1H2ello worl4d3"), 0);
    pEXPECT(tbs_insert(&a, -1, b));
    iEXPECT(strcmp(tbs_get_str(a), "1H2ello worl4d34"), 0);
    tbs_free(&a);
    tbs_free(&b);

    header("tbs_trunc", errors);
    iEXPECT(tbs_trunc(NULL, 5), 0);
    pEXPECT(a = tbs_new(TEST_STRING));
    iEXPECT(tbs_trunc(a, 5), 0);
    iEXPECT(strcmp(tbs_get_str(a), "Hello"), 0);
    pEXPECT(tbs_set_str(&a, TEST_STRING));
    iEXPECT(tbs_trunc(a, strlen(TEST_STRING) + 100), 0);
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING), 0);
    pEXPECT(tbs_set_str(&a, TEST_STRING));
    iEXPECT(tbs_trunc(a, -1), 0);
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING), 0);
    pEXPECT(tbs_set_str(&a, ""));
    iEXPECT(tbs_trunc(a, 0), 0);
    iEXPECT(strcmp(tbs_get_str(a), ""), 0);
    tbs_free(&a);

    header("tbs_rtrim", errors);
    iEXPECT(tbs_rtrim(NULL), 0);
    pEXPECT(a = tbs_new(TEST_STRING));
    iEXPECT(tbs_rtrim(a), 0);
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING), 0);
    pEXPECT(tbs_set_str(&a, TEST_STRING "   "));
    iEXPECT(tbs_rtrim(a), 0);
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING), 0);
    pEXPECT(tbs_set_str(&a, "   \t\n\r\f\v"));
    iEXPECT(tbs_rtrim(a), 0);
    iEXPECT(strcmp(tbs_get_str(a), ""), 0);
    pEXPECT(tbs_set_str(&a, ""));
    iEXPECT(tbs_rtrim(a), 0);
    iEXPECT(strcmp(tbs_get_str(a), ""), 0);
    tbs_free(&a);

    header("tbs_ltrim", errors);
    iEXPECT(tbs_ltrim(NULL), 0);
    pEXPECT(a = tbs_new(TEST_STRING));
    iEXPECT(tbs_ltrim(a), 0);
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING), 0);
    pEXPECT(tbs_set_str(&a, "   \t\n\r\f\v" TEST_STRING));
    iEXPECT(tbs_ltrim(a), 0);
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING), 0);
    pEXPECT(tbs_set_str(&a, "   \t\n\r\f\v"));
    iEXPECT(tbs_ltrim(a), 0);
    iEXPECT(strcmp(tbs_get_str(a), ""), 0);
    pEXPECT(tbs_set_str(&a, ""));
    iEXPECT(tbs_ltrim(a), 0);
    iEXPECT(strcmp(tbs_get_str(a), ""), 0);
    tbs_free(&a);

    header("tbs_trim", errors);
    iEXPECT(tbs_trim(NULL), 0);
    pEXPECT(a = tbs_new(TEST_STRING));
    iEXPECT(tbs_trim(a), 0);
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING), 0);
    pEXPECT(tbs_set_str(&a, "   \t\n\r\f\v" TEST_STRING "   \t\n\r\f\v" ));
    iEXPECT(tbs_trim(a), 0);
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING), 0);
    pEXPECT(tbs_set_str(&a, "   \t\n\r\f\v"));
    iEXPECT(tbs_trim(a), 0);
    iEXPECT(strcmp(tbs_get_str(a), ""), 0);
    pEXPECT(tbs_set_str(&a, ""));
    iEXPECT(tbs_trim(a), 0);
    iEXPECT(strcmp(tbs_get_str(a), ""), 0);
    tbs_free(&a);

    header("tbs_lastchar", errors);
    iEXPECT(tbs_lastchar(NULL), 0);
    pEXPECT(a = tbs_new(TEST_STRING));
    iEXPECT(tbs_lastchar(a), 'd');
    pEXPECT(tbs_set_str(&a, ""));
    iEXPECT(tbs_lastchar(a), 0);
    tbs_free(&a);

    header("tbs_upper", errors);
    tbs_upper(NULL);
    pEXPECT(a = tbs_new(""));
    tbs_upper(a);
    iEXPECT(strcmp(tbs_get_str(a), ""), 0);
    pEXPECT(tbs_set_str(&a, TEST_STRING));
    tbs_upper(a);
    iEXPECT(strcmp(tbs_get_str(a), "HELLO WORLD"), 0);
    tbs_free(&a);
    
    header("tbs_lower", errors);
    tbs_lower(NULL);
    pEXPECT(a = tbs_new(""));
    tbs_lower(a);
    iEXPECT(strcmp(tbs_get_str(a), ""), 0);
    pEXPECT(tbs_set_str(&a, TEST_STRING));
    tbs_lower(a);
    iEXPECT(strcmp(tbs_get_str(a), "hello world"), 0);
    tbs_free(&a);
    
    header("tbs_strcmp", errors);
    iEXPECT(tbs_strcmp(NULL, NULL), 0); /* both NULL */
    pEXPECT(a = tbs_new(TEST_STRING));
    piEXPECT(tbs_strcmp(a, NULL)); /* second arg NULL */
    niEXPECT(tbs_strcmp(NULL, a)); /* first arg NULL */
    pEXPECT(b = tbs_new(TEST_STRING));
    iEXPECT(tbs_strcmp(a, b), 0); /* same strings */
    pEXPECT(tbs_set_str(&b, ""));
    piEXPECT(tbs_strcmp(a, b)); /* b is empty */
    pEXPECT(tbs_set_str(&b, "z"));
    niEXPECT(tbs_strcmp(a, b)); /* b == "z" */
    pEXPECT(tbs_set_str(&b, "Hello"));
    piEXPECT(tbs_strcmp(a, b));
    tbs_free(&a);
    tbs_free(&b);
    
    header("tbs_strncmp", errors);
    iEXPECT(tbs_strncmp(NULL, NULL, 100), 0); /* both NULL */
    pEXPECT(a = tbs_new(TEST_STRING));
    piEXPECT(tbs_strncmp(a, NULL, 100)); /* second arg NULL */
    niEXPECT(tbs_strncmp(NULL, a, 100)); /* first arg NULL */
    pEXPECT(b = tbs_new(TEST_STRING));
    iEXPECT(tbs_strncmp(a, b, strlen(TEST_STRING)), 0); /* same strings */
    iEXPECT(tbs_strncmp(a, b, 5), 0); /* same strings */
    pEXPECT(tbs_set_str(&b, ""));
    piEXPECT(tbs_strncmp(a, b, strlen(TEST_STRING))); /* b is empty */
    pEXPECT(tbs_set_str(&b, "a"));
    niEXPECT(tbs_strncmp(a, b, strlen(TEST_STRING)));
    pEXPECT(tbs_set_str(&b, "Hello"));
    iEXPECT(tbs_strncmp(a, b, 5), 0);
    tbs_free(&a);
    tbs_free(&b);

    header("tbs_strcasecmp", errors);
    iEXPECT(tbs_strcasecmp(NULL, NULL), 0); /* both NULL */
    pEXPECT(a = tbs_new(TEST_STRING));
    iEXPECT(tbs_strcasecmp(a, NULL), 1); /* second arg NULL */
    iEXPECT(tbs_strcasecmp(NULL, a), -1); /* first arg NULL */
    tbs_free(&a);
    pEXPECT(a = tbs_new(TEST_STRING));
    pEXPECT(b = tbs_new(TEST_STRING));
    iEXPECT(tbs_strcasecmp(a, b), 0);
    iEXPECT(tbs_strcasecmp(a, b), 0);
    pEXPECT(tbs_set_str(&b, ""));
    piEXPECT(tbs_strcasecmp(a, b));
    pEXPECT(tbs_set_str(&b, "a"));
    piEXPECT(tbs_strcasecmp(a, b));
    tbs_free(&a);
    tbs_free(&b);
    
    header("tbs_strncasecmp", errors);
    iEXPECT(tbs_strncasecmp(NULL, NULL, 100), 0); /* both NULL */
    pEXPECT(a = tbs_new(TEST_STRING));
    piEXPECT(tbs_strncasecmp(a, NULL, 100)); /* second arg NULL */
    niEXPECT(tbs_strncasecmp(NULL, a, 100)); /* first arg NULL */
    pEXPECT(b = tbs_new(TEST_STRING));
    iEXPECT(tbs_strncasecmp(a, b, strlen(TEST_STRING)), 0);
    pEXPECT(tbs_set_str(&b, ""));
    piEXPECT(tbs_strncasecmp(a, b, strlen(TEST_STRING)));
    pEXPECT(tbs_set_str(&b, "a"));
    piEXPECT(tbs_strncasecmp(a, b, strlen(TEST_STRING)));
    pEXPECT(tbs_set_str(&b, "HELLO"));
    iEXPECT(tbs_strncasecmp(a, b, 5), 0);
    tbs_free(&a);
    tbs_free(&b);

    header("tbs_strcpy", errors);

    header("tbs_strcat", errors);
    pEXPECT(a = tbs_strcat(NULL, NULL));
    tbs_free(&a);
    pEXPECT(a = tbs_strcat(&a, NULL));
    pEXPECT(a = tbs_strcat(&a, NULL));

    pEXPECT(b = tbs_new(TEST_STRING));
    pEXPECT(tbs_strcat(&a, b));
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING), 0);

    pEXPECT(tbs_strcat(&a, b));
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING TEST_STRING), 0);

    tbs_free(&a);
    tbs_free(&b);

    pEXPECT(a = tbs_new(TEST_STRING));
    pEXPECT(tbs_strcat(&a, a));
    iEXPECT(strcmp(tbs_get_str(a), TEST_STRING TEST_STRING), 0);
    tbs_free(&a);

    header("tbs_strchr", errors);
    iEXPECT(tbs_strchr(NULL, 'l'), -1);
    pEXPECT(a = tbs_new(TEST_STRING));
    iEXPECT(tbs_strchr(a, 'l'), 2);
    iEXPECT(tbs_strchr(a, 'z'), -1);
    tbs_free(&a);

    header("tbs_strrchr", errors);
    iEXPECT(tbs_strrchr(NULL, 'l'), -1);
    pEXPECT(a = tbs_new(TEST_STRING));
    iEXPECT(tbs_strrchr(a, 'l'), 9);
    iEXPECT(tbs_strrchr(a, 'z'), -1);
    tbs_free(&a);

    header("tbs_strdup", errors);
    pEXPECT(b = tbs_strdup(NULL));
    iEXPECT(strcmp(tbs_get_str(b), ""), 0);
    tbs_free(&b);
    pEXPECT(a = tbs_new(TEST_STRING));
    pEXPECT(b = tbs_strdup(a));
    iEXPECT(tbs_strcmp(a, b), 0);
    tbs_free(&a);
    tbs_free(&b);

    header("tbs_strspn", errors);
    iEXPECT(tbs_strspn(NULL, NULL), 0);
    pEXPECT(a = tbs_new(TEST_STRING));
    pEXPECT(b = tbs_new("Hel"));
    iEXPECT(tbs_strspn(NULL, b), 0);
    iEXPECT(tbs_strspn(a, NULL), 0);
    iEXPECT(tbs_strspn(a, b), 4);
    pEXPECT(tbs_set_str(&b, "TB"));
    iEXPECT(tbs_strspn(a, b), 0);
    tbs_free(&a);
    tbs_free(&b);

    header("tbs_strcspn", errors);
    iEXPECT(tbs_strcspn(NULL, NULL), 0);
    pEXPECT(a = tbs_new(TEST_STRING));
    pEXPECT(b = tbs_new("o"));
    iEXPECT(tbs_strcspn(NULL, b), 0);
    iEXPECT(tbs_strcspn(a, NULL), 0);
    iEXPECT(tbs_strcspn(a, b), 4);
    pEXPECT(tbs_set_str(&b, "TB"));
    iEXPECT(tbs_strcspn(a, b), 11);
    tbs_free(&a);
    tbs_free(&b);

    header("tbs_strtok", errors);
    pos = 5;
    npEXPECT(tbs_strtok(NULL, &pos, NULL));
    pEXPECT(a = tbs_new(""));
    pEXPECT(b = tbs_new(""));
    npEXPECT(tbs_strtok(NULL, &pos, b));
    npEXPECT(tbs_strtok(a, &pos, NULL));
    npEXPECT(tbs_strtok(NULL, &pos, b));
    npEXPECT(tbs_strtok(a, &pos, b));
    pEXPECT(tbs_set_str(&a, TEST_STRING));
    npEXPECT(c = tbs_strtok(a, &pos, b));
    npEXPECT(c = tbs_strtok(b, &pos, a));

    pEXPECT(tbs_set_str(&b, "l"));
    pos = 0;
    pEXPECT(c = tbs_strtok(a, &pos, b));
    iEXPECT(strcmp(tbs_get_str(c), "He"), 0);
    iEXPECT(pos, 3);
    tbs_free(&c);

    pEXPECT(tbs_set_str(&b, " "));
    pEXPECT(c = tbs_strtok(a, &pos, b));
    iEXPECT(strcmp(tbs_get_str(c), "lo"), 0);
    iEXPECT(pos, 6);
    tbs_free(&c);

    pEXPECT(c = tbs_strtok(a, &pos, b));
    iEXPECT(strcmp(tbs_get_str(c), "world"), 0);
    iEXPECT(pos, 11);
    tbs_free(&c);
        
    tbs_free(&a);
    tbs_free(&b);

    header("tbs_strstr", errors);
    iEXPECT(tbs_strstr(NULL, NULL), -1);
    pEXPECT(a = tbs_new(TEST_STRING));
    iEXPECT(tbs_strstr(a, NULL), -1);
    iEXPECT(tbs_strstr(NULL, a), -1);
    iEXPECT(tbs_strstr(a, a), 0);
    pEXPECT(b = tbs_new("llo"));
    iEXPECT(tbs_strstr(a, b), 2);
    tbs_free(&a);
    tbs_free(&b);

    header("tbs_sprintf", errors);
    pEXPECT(a = tbs_new(TEST_STRING));
    iEXPECT(tbs_sprintf(&a, "%d", 2344), 4);
    iEXPECT(strcmp(tbs_get_str(a), "2344"), 0);
    liEXPECT(tbs_sprintf(&a, "%s", TEST_STRING TEST_STRING), 
            strlen(TEST_STRING TEST_STRING));
    pEXPECT(tbs_set_str(&b, TEST_STRING TEST_STRING));
    iEXPECT(tbs_strcmp(a, b), 0);
    tbs_free(&a);
    iEXPECT(tbs_sprintf(&a, "%d", 2344), 4); /* a == NULL */
    iEXPECT(strcmp(tbs_get_str(a), "2344"), 0);
    tbs_free(&a);
    tbs_free(&b);

    header("finished", errors);
end:
    
    return(errors);
}

int main(int argc, char **argv)
{
    int errors = 0;
    int value;
    tbs a = NULL;
    tbs b = NULL;
    tbs c = NULL;

    setenv("TBSTRING_CHECK", "1", 2);

    errors += unittest(1);
    errors += unittest(23);
    errors += unittest(4090);

    header("tbs_check", errors);
    pEXPECT(a = tbs_new("asd"));
    memcpy(a, "mmm", 4); /* overwrite the first sentinel */
    npEXPECT(b = tbs_strdup(a));
    xfree(a); a = NULL;
    xfree(b); b = NULL;

    {
        size_t physize;
        size_t *sentinel2;

        pEXPECT(a = tbs_new("asd"));
        physize = ((a->maxlen + 1) / sizeof(size_t)) + 4;
        sentinel2 = (size_t *) a + physize - 1;

        memcpy(sentinel2, "mmm", 4); /* overwrite the second sentinel */
    }
    npEXPECT(b = tbs_strdup(a));
    iEXPECT(tbs_get_maxlen(a), -1);
    xfree(a); a = NULL;
    xfree(b); b = NULL;

end:
    printf("%d error%c\n", errors, errors == 1? ' ': 's');
    return(errors);
}

